# -*- coding = utf-8 -*-
# @Time : 2021/1/4 15:13
# @Author : Panda
# @File : gui_model.py
# @Software : PyCharm

import numpy as np
import tkinter.messagebox
import matplotlib.pyplot as plt
from tkinter import Tk, Label, W, Button, E, Entry, IntVar
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# 创建窗口
windows = Tk(className="传感网课程设计")
windows.configure(background="#f0f0f0")

# 设置窗口的大小
windows.geometry("1300x900+200+60")

# “参数” 文本内容显示窗口
Label(windows, text="|参数|", font=("Microsoft YaHei", 12), height=2).grid(row=0, column=0)
Label(windows, text="|仿真图|", font=("Microsoft YaHei", 12), height=2).place(x=410, y=1)
Label(windows, text="最大子节点数Cm", font=("Microsoft YaHei", 10), height=2).grid(row=1, column=0)
Label(windows, text="最大ZR拥有数Rm", font=("Microsoft YaHei", 10), height=2).grid(row=2, column=0)
Label(windows, text="最大树深Lm", font=("Microsoft YaHei", 10), height=2).grid(row=3, column=0)
Label(windows, text="ZC数量", font=("Microsoft YaHei", 10), height=2).grid(row=4, column=0)
Label(windows, text="ZR数量", font=("Microsoft YaHei", 10), height=2).grid(row=5, column=0)
Label(windows, text="ZED数量", font=("Microsoft YaHei", 10), height=2).grid(row=6, column=0)
Label(windows, text="无线通信距离", font=("Microsoft YaHei", 10), height=2).grid(row=7, column=0)

Label(windows, text="|路径搜寻|", font=("Microsoft YaHei", 12), height=2).grid(row=12, column=0)
Label(windows, text="源节点地址", font=("Microsoft YaHei", 10), height=2).grid(row=13, column=0)
Label(windows, text="目的节点地址", font=("Microsoft YaHei", 10), height=2).grid(row=14, column=0)

# 默认值类型
a = IntVar()
b = IntVar()
c = IntVar()
d = IntVar()
e = IntVar()
f = IntVar()
g = IntVar()

# 单行文本输入框
input_Cm = Entry(windows, font=("Microsoft YaHei", 10), textvariable=a)
input_Rm = Entry(windows, font=("Microsoft YaHei", 10), textvariable=b)
input_Lm = Entry(windows, font=("Microsoft YaHei", 10), textvariable=c)
input_ZC = Entry(windows, font=("Microsoft YaHei", 10), textvariable=d, state='disabled')
input_ZR = Entry(windows, font=("Microsoft YaHei", 10), textvariable=e)
input_ZED = Entry(windows, font=("Microsoft YaHei", 10), textvariable=f)
input_distance = Entry(windows, font=("Microsoft YaHei", 10), textvariable=g)
input_sourceAddress = Entry(windows, font=("Microsoft YaHei", 10))
input_destinationAddress = Entry(windows, font=("Microsoft YaHei", 10))

# 设置默认值
a.set(4)
b.set(3)
c.set(3)
d.set(1)
e.set(33)
f.set(12)
g.set(200)

# 文本框定位
input_Cm.grid(row=1, column=1)
input_Rm.grid(row=2, column=1)
input_Lm.grid(row=3, column=1)
input_ZC.grid(row=4, column=1)
input_ZR.grid(row=5, column=1)
input_ZED.grid(row=6, column=1)
input_distance.grid(row=7, column=1)
input_sourceAddress.grid(row=13, column=1)
input_destinationAddress.grid(row=14, column=1)

# 图像及画布设置
fig = plt.figure(figsize=(8, 8), dpi=100)
# 在窗口显示图像
canvas_spice = FigureCanvasTkAgg(fig, windows)
# 放置位置
canvas_spice.get_tk_widget().place(x=410, y=50)


def get_nwkMaxChildren():
    """获取最大子结点数 Cm"""
    try:
        var1 = input_Cm.get()
        if var1 is not None:
            return int(var1)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def get_nwkMaxRouters():
    """获取最大路由结点数 Rm"""
    try:
        var2 = input_Rm.get()
        if var2 is not None:
            return int(var2)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def get_nwkMaxDepth():
    """获取树的最大深度 Lm"""
    try:
        var3 = input_Lm.get()
        if var3 is not None:
            return int(var3)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def get_ZigBeeCoordinator():
    """获取ZigBee协调器ZC数量"""
    try:
        var4 = input_ZC.get()
        if var4 is not None:
            return int(var4)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def get_ZigBeeRouter():
    """获取ZigBee路由器ZR数量"""
    try:
        var5 = input_ZR.get()
        if var5 is not None:
            return int(var5)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def get_ZigBeeEndDevice():
    """获取ZigBee终端设备ZED数量"""
    try:
        var6 = input_ZED.get()
        if var6 is not None:
            return int(var6)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def get_WSNDistance():
    """获取无线通信距离"""
    try:
        var7 = input_distance.get()
        if var7 is not None:
            return int(var7)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def get_SNAddress():
    """获取源节点地址"""
    try:
        var8 = input_sourceAddress.get()
        if var8 is not None:
            return int(var8)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def get_DNAddress():
    """获取目的节点地址"""
    try:
        var9 = input_destinationAddress.get()
        if var9 is not None:
            return int(var9)
    except ValueError:
        tkinter.messagebox.showwarning('警告', '输入框不能为空！！')
        return 0


def connection_zed(zed_coordinate, r, parent_x, parent_y, visited, depth, ip_dict, node_dict):
    """连接终端设备"""
    if get_nwkMaxDepth() > 0:
        # 临时存放当前父节点连接的终端坐标
        child_temp = []
        coordinate_node = str([parent_x, parent_y])
        for item in zed_coordinate:
            distances = (parent_x - item[0]) ** 2 + (parent_y - item[1]) ** 2
            if len(child_temp) >= get_nwkMaxChildren() - get_nwkMaxRouters():
                break
            if distances <= r and item not in visited:
                plt.plot([parent_x, item[0]], [parent_y, item[1]], color='black',
                         linewidth=1)
                child_temp.append(item)
                visited.append(item)

        for key in node_dict.keys():
            if coordinate_node == key:
                temp_list = node_dict[coordinate_node]
                for item in child_temp:
                    temp_list.append(item)
                node_dict.update({coordinate_node: temp_list})

        for i in range(len(child_temp)):
            if depth == 0:
                parentAddress = str([400, 400])
                ip = calculation_ZED_Address(ip_dict[parentAddress], depth, i + 1)
            else:
                parentAddress1 = str([parent_x, parent_y])
                ip = calculation_ZED_Address(ip_dict[parentAddress1], depth, i + 1)
            coordinate = str(child_temp[i])
            ip_dict[coordinate] = [ip]
            plt.annotate(ip, (child_temp[i][0], child_temp[i][1]))
    return node_dict


def connection_ZR(ZR_coordinate, r, depth, parent_x, parent_y, zed_coordinate, visited, ip_dict, node_dict):
    """递归实现ZR连接线"""

    # 存放所有已连接的ZR坐标
    conn_ZR_Coordinates = []
    # 存放Lm-1层ZR的坐标
    ZR_Coordinates_part = []
    # 存放生成node_dict的value（父节点的ZR子节点）
    child_dict_value = []

    coordinate_node = str([parent_x, parent_y])
    # 找当前父节点的子路由节点坐标
    for item in ZR_coordinate:
        distances = (parent_x - item[0]) ** 2 + (parent_y - item[1]) ** 2
        # 判断是否满足用户输入的最大路由器节点个数以及最大深度
        if len(conn_ZR_Coordinates) >= get_nwkMaxRouters() or get_nwkMaxDepth() == 0:
            break
        # 判断当前遍历到的节点距离是否小于传感器感测半径
        if distances <= r:
            plt.plot([parent_x, item[0]], [parent_y, item[1]], color='black', linewidth=1)
            conn_ZR_Coordinates.append(item)
            child_dict_value.append(item)
            if depth < get_nwkMaxDepth() - 1:
                ZR_Coordinates_part.append(item)

    node_dict[coordinate_node] = child_dict_value
    # 在字典里加入当前自路由节点坐标，其value为空，表示还未连接子节点
    for item in child_dict_value:
        temp = str(item)
        node_dict[temp] = []
    # 计算当前父节点的子路由节点IP并显示
    for i in range(len(conn_ZR_Coordinates)):
        if depth == 0:
            parentAddress = str([400, 400])
            ip = calculation_ZR_Address(ip_dict[parentAddress], depth, i + 1)
        else:
            parentAddress1 = str([parent_x, parent_y])
            ip = calculation_ZR_Address(ip_dict[parentAddress1], depth, i + 1)
        coordinate = str(conn_ZR_Coordinates[i])
        ip_dict[coordinate] = [ip]
        plt.annotate(ip, (conn_ZR_Coordinates[i][0], conn_ZR_Coordinates[i][1]))
    # 调用connection_zed函数寻找当前父节点的子终端节点
    if len(ZR_Coordinates_part) < get_nwkMaxChildren() and depth < get_nwkMaxDepth():
        if depth == 0:
            node_dict = connection_zed(zed_coordinate, r, 400, 400, visited, depth, ip_dict, node_dict)
        else:
            node_dict = connection_zed(zed_coordinate, r, parent_x, parent_y, visited, depth, ip_dict, node_dict)
    # 递归出入口
    if len(conn_ZR_Coordinates) == 0:
        return node_dict, ip_dict
    else:
        depth += 1
        for k in range(len(conn_ZR_Coordinates)):
            ZR_coordinate.remove(conn_ZR_Coordinates[k])
        for item in conn_ZR_Coordinates:
            if depth >= get_nwkMaxDepth():
                break
            connection_ZR(ZR_coordinate, r, depth, item[0], item[1], zed_coordinate, visited, ip_dict, node_dict)
        return node_dict, ip_dict


def draw_sensor_and_path(ZR_coordinate, ZED_coordinate, flag=0):
    """绘制传感器和绘制路径"""
    # 绘制路径
    if flag == 1:
        plt.plot([ZR_coordinate[0], ZED_coordinate[0]], [ZR_coordinate[1], ZED_coordinate[1]], color='r',
                 linestyle='-.', linewidth=3)
    else:
        # 绘制ZC
        ZC_point = plt.scatter(400, 400, s=300, c='red', marker='^')
        plt.annotate(0, (400, 400))

        # 绘制ZR
        ZR_coordinate_T = ZR_coordinate.T
        ZR_point = plt.scatter(ZR_coordinate_T[0], ZR_coordinate_T[1], s=300, c='#1E90FF', marker='s')

        # 绘制ZED
        ZED_coordinate_T = ZED_coordinate.T
        ZED_point = plt.scatter(ZED_coordinate_T[0], ZED_coordinate_T[1], s=300, c='#00EE76', marker='o')

        # 显示图例
        plt.legend(handles=[ZC_point, ZR_point, ZED_point], labels=['ZC', 'ZR', 'ZED'], loc='upper left',
                   markerscale=0.5,
                   framealpha=0.5)

    # 将matplotlib画的图显示到窗口中
    plt.axis('off')
    plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
    plt.xlim(0, 800)
    plt.ylim(0, 800)
    plt.margins(0, 0)
    canvas_spice.draw()


def calculation_skip(depth):
    """计算间隔值"""
    Cm = get_nwkMaxChildren()
    Rm = get_nwkMaxRouters()
    Lm = get_nwkMaxDepth()
    if get_nwkMaxRouters() == 1:
        skip = 1 + Cm * (Lm - depth - 1)
    elif depth == Lm:
        skip = 0
    else:
        skip = (1 + Cm - Rm - Cm * (Rm ** (Lm - depth - 1))) / (1 - Rm)
    return int(skip)


def calculation_ZR_Address(parentAddress, depth, nth):
    """计算ZR节点地址"""
    return int(parentAddress[0] + 1 + (nth - 1) * calculation_skip(depth))


def calculation_ZED_Address(parentAddress, depth, nth):
    """计算ZED节点地址"""
    Rm = get_nwkMaxRouters()
    return int(parentAddress[0] + calculation_skip(depth) * Rm + nth)


def create_ZBSensor():
    """创建ZigBee传感器"""
    plt.clf()
    r = get_WSNDistance() ** 2
    visited = []
    ip_dict = {'[400, 400]': [0]}
    node_dict = {}

    # 随机生成ZR坐标
    ZR_coordinate = np.random.randint(0, 800, size=(get_ZigBeeRouter(), 2))
    ZR_list = ZR_coordinate.tolist()

    # 随机生成ZED坐标
    ZED_coordinate = np.random.randint(0, 800, size=(get_ZigBeeEndDevice(), 2))
    ZED_list = ZED_coordinate.tolist()

    global node_dict1
    global ip_dict1
    node_dict1, ip_dict1 = connection_ZR(ZR_list, r, 0, 400, 400, ZED_list, visited, ip_dict, node_dict)

    draw_sensor_and_path(ZR_coordinate, ZED_coordinate)


def get_dict_key(source_dict, value):
    """通过字典的value获取key"""
    for temp_key, temp_value in source_dict.items():
        if (value in temp_value) or (value == temp_value):
            x, y = temp_key.split(',')
            x = x.replace('[', '')
            y = y.replace(']', '')
            return [int(x), int(y)]
    return None


def find_drawPath():
    """根据地址寻找并描绘路径"""
    source_node_address = get_SNAddress()
    destination_node_address = get_DNAddress()
    source_path_list = []
    destination_path_list = []

    coordinate_source = get_dict_key(ip_dict1, source_node_address)
    coordinate_destination = get_dict_key(ip_dict1, destination_node_address)

    # 从源节点寻找到ZC结点
    while True:
        source_path_list.append(coordinate_source)
        if coordinate_source != [400, 400]:
            coordinate_source_father = get_dict_key(node_dict1, coordinate_source)
            draw_sensor_and_path(coordinate_source, coordinate_source_father, 1)
        else:
            draw_sensor_and_path(coordinate_source, [400, 400], 1)
            break
        coordinate_source = coordinate_source_father

    # 从目的节点寻找到ZC结点
    while True:
        destination_path_list.append(coordinate_destination)
        if coordinate_destination != [400, 400]:
            coordinate_destination_father = get_dict_key(node_dict1, coordinate_destination)
            draw_sensor_and_path(coordinate_destination, coordinate_destination_father, 1)
        else:
            draw_sensor_and_path(coordinate_destination, [400, 400], 1)
            break
        coordinate_destination = coordinate_destination_father

def quit_windows():
    """退出窗口界面"""
    windows.quit()


# 创建ZigBe传感器按钮
Button(windows, text="创建ZigBee传感器", command=create_ZBSensor, font=("Microsoft YaHei", 10), height=1,
       bg="#4e6ef2",
       overrelief='sunken').grid(row=8, column=1, columnspan=1, rowspan=1, pady=40, sticky=W + E)

# 寻找并描绘路径按钮
Button(windows, text="寻找并描绘路径", command=find_drawPath, font=("Microsoft YaHei", 10), height=1,
       bg="#4e6ef2",
       overrelief='sunken').grid(row=16, column=1, columnspan=1, rowspan=1, pady=40, sticky=W + E)

# 退出窗口按钮
Button(windows, text="退出", command=quit_windows, font=("Microsoft YaHei", 10), height=1, bg="#FF6666",
       overrelief='sunken').grid(row=18, column=1, columnspan=1, rowspan=1, sticky=W + E)

# 开启Tkinter主循环
windows.mainloop()